package custom

import (
	"encoding/binary"
	"encoding/hex"
	"sync/atomic"
)

type (
	CustomUUID struct {
		cfg   *Options
		mid   uint32
		count uint32
	}
)

func (c *CustomUUID) New() [16]byte {
	var res [16]byte
	var mid uint16

	num := atomic.AddUint32(&c.count, 1)
	if num == 0 {
		mid = uint16(atomic.AddUint32(&c.mid, 1) & 0x0000ffff)
	} else {
		mid = uint16(atomic.LoadUint32(&c.mid) & 0x0000ffff)
	}

	if mid == 0 && num == 0 {
		panic("INVALID ID GEN ")
	}

	binary.BigEndian.PutUint16(res[0:4], c.cfg.Seed)
	binary.BigEndian.PutUint16(res[4:], mid)
	binary.BigEndian.PutUint32(res[8:], num)

	return res
}

func (c *CustomUUID) New32UUID() uint32 {

	num := atomic.AddUint32(&c.count, 1) & 0x0000ffff
	if num == 0 {
		panic("INVALID ID GEN")
	}
	return uint32(c.cfg.Seed)<<16 + num
}

func (c *CustomUUID) New64UUID() uint64 {
	var mid uint16
	num := atomic.AddUint32(&c.count, 1)
	if num == 0 {
		mid = uint16(atomic.AddUint32(&c.mid, 1) & 0x0000ffff)
	} else {
		mid = uint16(atomic.LoadUint32(&c.mid) & 0x0000ffff)
	}
	return uint64(c.cfg.Seed)<<48 + uint64(mid)<<32 + uint64(num)
}

// NewStringUUID will return rfc 2141 uuid
func (c *CustomUUID) NewStringUUID() string {
	var dist [36 + 9]byte
	res := c.New()
	hex.Encode(dist[:], res[0:4])
	dist[8] = '-'
	hex.Encode(dist[9:13], res[4:6])
	dist[13] = '-'
	hex.Encode(dist[14:18], res[6:8])
	dist[18] = '-'
	hex.Encode(dist[19:23], res[8:10])
	dist[23] = '-'
	hex.Encode(dist[24:], res[10:])
	return string(dist[:])
}

func (c *CustomUUID) ParseFromByte(bytes []byte) string {
	panic("implement me")
}

func NewCustomUUID(opts ...Option) *CustomUUID {
	idGen := &CustomUUID{
		cfg:   &Options{},
		mid:   1,
		count: 1,
	}

	for _, opt := range opts {
		opt(idGen.cfg)
	}
	return idGen
}
